package org.zmhhxl.sample3.oauth2.a.authentications.model.json;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.MissingNode;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.zmhhxl.sample3.oauth2.a.authentications.model.CustomUserDetails;

import java.io.IOException;
import java.util.Set;

/**
 * Custom Deserializer for {@link User} class. This is already registered with
 * {@link CustomUserDetailsMixin}. You can also use it directly with your mixin class.
 *
 * @author Jitendra Singh
 * @see CustomUserDetailsMixin
 * @since 4.2
 */
public class CustomUserDetailsDeserializer extends JsonDeserializer<CustomUserDetails> {
   private static final TypeReference<Set<SimpleGrantedAuthority>> SIMPLE_GRANTED_AUTHORITY_SET = new TypeReference<Set<SimpleGrantedAuthority>>() {
   };

   /**
    * This method will create {@link User} object. It will ensure successful object
    * creation even if password key is null in serialized json, because credentials may
    * be removed from the {@link User} by invoking {@link User#eraseCredentials()}. In
    * that case there won't be any password key in serialized json.
    * @param jp the JsonParser
    * @param ctxt the DeserializationContext
    * @return the user
    * @throws IOException if a exception during IO occurs
    * @throws JsonProcessingException if an error during JSON processing occurs
    */
   @Override
   public CustomUserDetails deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
      ObjectMapper mapper = (ObjectMapper) jp.getCodec();
      JsonNode jsonNode = mapper.readTree(jp);
      Set<? extends GrantedAuthority> authorities = mapper.convertValue(jsonNode.get("authorities"),
            SIMPLE_GRANTED_AUTHORITY_SET);
      JsonNode passwordNode = readJsonNode(jsonNode, "password");

      Long userId = readJsonNode(jsonNode, "userId").asLong();
      Integer dataScope = readJsonNode(jsonNode, "dataScope").asInt();
      Long deptId = readJsonNode(jsonNode, "deptId").asLong();

      String username = readJsonNode(jsonNode, "username").asText();
      String password = passwordNode.asText("");
      boolean enabled = readJsonNode(jsonNode, "enabled").asBoolean();
      boolean accountNonExpired = readJsonNode(jsonNode, "accountNonExpired").asBoolean();
      boolean credentialsNonExpired = readJsonNode(jsonNode, "credentialsNonExpired").asBoolean();
      boolean accountNonLocked = readJsonNode(jsonNode, "accountNonLocked").asBoolean();
      CustomUserDetails result = new CustomUserDetails(userId, username, password, enabled, dataScope, deptId, accountNonExpired, credentialsNonExpired, accountNonLocked,
            authorities);
      if (passwordNode.asText(null) == null) {
         result.eraseCredentials();
      }
      return result;
   }

   private JsonNode readJsonNode(JsonNode jsonNode, String field) {
      return jsonNode.has(field) ? jsonNode.get(field) : MissingNode.getInstance();
   }
}
